/*
 * Cliente.cpp
 *
 *  Created on: 04/10/2012
 *      Author: leandro
 */

#include "Cliente.h"
#include <iostream>

#define UPDATE_JUGANDO 0
#define UPDATE_FIN_JUEGO 1


Cliente::Cliente() {
	pantalla = NULL;
	vista = NULL;
	rectPersonaje = NULL;
	imagenFondo = -1;
	controlador = NULL;
	largo = 0;
	socket = -1;
	numJugador = -1;
	mensajes = "";
	estadoConexion = Inactiva;
	estadoJuego = JuegoEnd;
	margenScroll = 200;
	
	pthread_mutex_init(&mutexDibujables, NULL);
	pthread_mutex_init(&mutexUpdates, NULL);
	pthread_mutex_init(&mutexPuntajes, NULL);
	pthread_mutex_init(&mutexSonidos, NULL);
	pthread_mutex_init(&mutexEstadoJuego, NULL);
	pthread_mutex_init(&mutexEstadoConexion, NULL);
}

Cliente::~Cliente() {
	
	delete controlador;
	disconectSocket(socket);
	pthread_mutex_destroy(&mutexDibujables);
	pthread_mutex_destroy(&mutexUpdates);
	pthread_mutex_destroy(&mutexPuntajes);
	pthread_mutex_destroy(&mutexSonidos);
	pthread_mutex_destroy(&mutexEstadoJuego);
	pthread_mutex_destroy(&mutexEstadoConexion);
	sincronizador.delete_sync_folder( PATH_SINCRONIZACION_CLIENTE );
}

void Cliente::setVista(int ancho, int alto, int scroll, int anchoNivel, int altoNivel) {
	pantalla = new Pantalla (ancho, alto, scroll);
	vista = new Vista (rectPersonaje, pantalla, anchoNivel, altoNivel);
}

void Cliente::setPersonaje(int posX, int posY, int ancho, int alto, int margen) {
	rectPersonaje = new SDL_Rect();
	rectPersonaje->x = posX;
	rectPersonaje->y = posY;
	rectPersonaje->w = ancho;
	rectPersonaje->h = alto;
	margenScroll=margen;
}

void Cliente::setImagenFondo(idImagen_t idImagenFondo) {
	imagenFondo = idImagenFondo;
}

void Cliente::setDibujables(vector< pair < pair<idImagen_t, int> , Punto> > v_dibujables) {
	
	pthread_mutex_lock(&mutexDibujables);
	dibujables = v_dibujables;
	pthread_mutex_unlock(&mutexDibujables);
}

void Cliente::addUpdate(string mensaje, char tipoMensaje) {
	
	int tipo;
	switch(tipoMensaje) {
		case 'U':
			tipo = UPDATE_JUGANDO;
			break;
		case 'F':
			tipo = UPDATE_FIN_JUEGO;
			break;
		default:
			tipo = UPDATE_JUGANDO;
	}
	
	pair<int, string> update(tipo, mensaje);
	
	pthread_mutex_lock(&mutexUpdates);
	updates.push(update);
	pthread_mutex_unlock(&mutexUpdates);
}

pair<int, string> Cliente::getNextUpdate() {
	
	pair<int, string> mensaje;
	
	pthread_mutex_lock(&mutexUpdates);
	if (!updates.empty()) {
		mensaje = updates.front();
		updates.pop();
	}
	pthread_mutex_unlock(&mutexUpdates);
	
	return mensaje;
}	

bool Cliente::Conectar(string host, string port) {

	if (conectSocket(&socket, host.c_str(), atoi(port.c_str())) == 2) return false;

    if (!sincronizar()) return false;
	cout << "salio de sincronizar" << endl << flush;
	setEstadoConexion(Activa);

	return true;
}

bool Cliente::sincronizar() {
	
	sincronizador.ClienteSincronizarConServidor(getSocket(), PATH_SINCRONIZACION_CLIENTE);
					
	return true;
}

bool Cliente::Comenzar(vector<infoImagen_t> infoImagenes, vector<infoSonido_t> infoSonidos, int personaje) {
	
	vista->CrearSuperficiePantalla();
	vista->InsertarCuadro();
	vista->CrearMapaSonidos(infoSonidos, PATH_SINCRONIZACION_CLIENTE, personaje);
	vista->CrearMapaImagenes(infoImagenes, PATH_SINCRONIZACION_CLIENTE);
	vista->CrearFondo(imagenFondo);

	for (unsigned int i=0; i<infoSonidos.size(); i++) {
		Punto posicion(0, 0);
		sonidos.push_back(pair< pair<bool, int>, Punto >(pair<bool, int>(false, infoSonidos.at(i).tipo), posicion));
	}
		
	controlador = new InputCliente();
	writeSocket(getSocket(), "OK#");
	t_juego.start();

	return true;
}

void Cliente::updatePersonaje(int posX, int posY) {
	vista->setPersonajeX(posX);
	vista->setPersonajeY(posY);
}

void Cliente::unSerializeDibujable(string mensaje) {

	vector< pair < pair<idImagen_t, int> , Punto> > dibujables;
	
	int jugadorX = atoi(mensaje.substr(0, mensaje.find_first_of(";")).c_str());
	mensaje.erase(0, mensaje.find_first_of(";")+1);
	
	int jugadorY = atoi(mensaje.substr(0, mensaje.find_first_of("<")).c_str());
	mensaje.erase(0, mensaje.find_first_of("<")+1);
	
	vista->setPersonajeX(jugadorX);
	vista->setPersonajeY(jugadorY);
	
	unsigned int cantidadDibujables = std::count(mensaje.begin(), mensaje.end(), '#');
	while (cantidadDibujables != 0) {
	
		idImagen_t idImagen = atoi(mensaje.substr(0, mensaje.find_first_of(";")).c_str());
		mensaje.erase(0, mensaje.find_first_of(";")+1);
		
		int cuadro = atoi(mensaje.substr(0, mensaje.find_first_of(";")).c_str());
		mensaje.erase(0, mensaje.find_first_of(";")+1);
		
		int dibujableX = atoi(mensaje.substr(0, mensaje.find_first_of(";")).c_str());
		mensaje.erase(0, mensaje.find_first_of(";")+1);
		
		int dibujableY = atoi(mensaje.substr(0, mensaje.find_first_of("#")).c_str());
		mensaje.erase(0, mensaje.find_first_of("#")+1);

		Punto posicion(dibujableX, dibujableY);
		pair<idImagen_t, int> imagen(idImagen, cuadro);
		pair< pair<idImagen_t, int>, Punto > dibujable(imagen, posicion);

		if (idImagen >= 0) dibujables.push_back(dibujable);
		--cantidadDibujables;
			
	}
	
	if (dibujables.size() > 0)
		setDibujables(dibujables);
}


void Cliente::unSerializeSonidos(string mensaje) {
	
	vector< pair < vector< pair<int, int> >, Punto> > sonidos;
	mensaje.erase(0, mensaje.find_first_of("<")+1);
	
	unsigned int cantidad = std::count(mensaje.begin(), mensaje.end(), '#');
	while (cantidad != 0) {
	
		string sonido = mensaje.substr(0, mensaje.find_first_of("#")+1);
		mensaje.erase(0, mensaje.find_first_of("#")+1);
		
		vector< pair<int, int> > idsSonidos;
		int cantidadSonidos = std::count(sonido.begin(), sonido.end(), ':');
		while (cantidadSonidos != 0) {
			
			idSonido_t idSonido = atoi(sonido.substr(0, sonido.find_first_of(";")).c_str());
			sonido.erase(0, sonido.find_first_of(";")+1);
			int tipo = atoi(sonido.substr(0, sonido.find_first_of(":")).c_str());
			sonido.erase(0, sonido.find_first_of(":")+1);
			
			idsSonidos.push_back(pair<int, int> (idSonido, tipo));
			--cantidadSonidos;
		}
		
		sonido.erase(0, sonido.find_first_of(";")+1);
		
		int sonidoX = atoi(sonido.substr(0, sonido.find_first_of(";")).c_str());
		sonido.erase(0, sonido.find_first_of(";")+1);
		
		int sonidoY = atoi(sonido.substr(0, sonido.find_first_of("#")).c_str());
		sonido.erase(0, sonido.find_first_of("#")+1);

		Punto posicion(sonidoX, sonidoY);
		sonidos.push_back(pair < vector< pair<int, int> >, Punto>(idsSonidos, posicion));
		--cantidad;		
	}

	setSonidos(sonidos);
}

void Cliente::unSerializePuntajes(string mensaje) {

	vector< pair <int, string> > puntajes;
	mensaje.erase(0, mensaje.find_first_of("<")+1);

	unsigned int cantidadDibujables = std::count(mensaje.begin(), mensaje.end(), '#');
	while (cantidadDibujables != 0) {
	
		idImagen_t idImagen = atoi(mensaje.substr(0, mensaje.find_first_of(";")).c_str());
		mensaje.erase(0, mensaje.find_first_of(";")+1);

		string descripcion = mensaje.substr(0, mensaje.find_first_of("#")).c_str();
		mensaje.erase(0, mensaje.find_first_of("#")+1);

		pair<idImagen_t, string> puntaje(idImagen, descripcion);

		puntajes.push_back(puntaje);
		--cantidadDibujables;
			
	}
	
	if (puntajes.size() > 0)
		setPuntajes(puntajes);
}

void Cliente::unSerializeObstaculos(string mensaje) {
	
	vector< pair < pair<idImagen_t, int> , Punto> > dibujables;
	mensaje.erase(0, mensaje.find_first_of("<")+1);
	
	unsigned int cantidadDibujables = std::count(mensaje.begin(), mensaje.end(), '#');
	while (cantidadDibujables != 0) {
	
		idImagen_t idImagen = atoi(mensaje.substr(0, mensaje.find_first_of(";")).c_str());
		mensaje.erase(0, mensaje.find_first_of(";")+1);
		
		int cuadro = atoi(mensaje.substr(0, mensaje.find_first_of(";")).c_str());
		mensaje.erase(0, mensaje.find_first_of(";")+1);
		
		int dibujableX = atoi(mensaje.substr(0, mensaje.find_first_of(";")).c_str());
		mensaje.erase(0, mensaje.find_first_of(";")+1);
		
		int dibujableY = atoi(mensaje.substr(0, mensaje.find_first_of("#")).c_str());
		mensaje.erase(0, mensaje.find_first_of("#")+1);

		Punto posicion(dibujableX, dibujableY);
		pair<idImagen_t, int> imagen(idImagen, cuadro);
		pair< pair<idImagen_t, int>, Punto > dibujable(imagen, posicion);

		dibujables.push_back(dibujable);
		--cantidadDibujables;
			
	}
	
	vista->PintarFondo(dibujables);
}
	
void Cliente::unSerializeUpdate(string mensaje, char tipoMensaje) {
	
	string update = mensaje.substr(mensaje.find_first_of("<")+1, mensaje.find_first_of(">") - mensaje.find_first_of("<")-1);
	
	if (update.size() > 0)
		addUpdate(update, tipoMensaje);
}

void Cliente::unSerialize(string cadena) {
	
	mensajes += cadena;
	unsigned int posSeparadorMensaje = mensajes.find_first_of(">");
	if (posSeparadorMensaje == string::npos) return;
	
	string mensajeAProcesar = mensajes.substr(0, posSeparadorMensaje+1);
	mensajes.erase(0, posSeparadorMensaje+1);		
	
	string tipoMensaje = mensajeAProcesar.substr(0, mensajeAProcesar.find_first_of("#"));
	mensajeAProcesar.erase(0, mensajeAProcesar.find_first_of("#")+1);
	
	if (tipoMensaje == "O") unSerializeObstaculos(mensajeAProcesar);
	else if (tipoMensaje == "D") {
		unSerializeDibujable(mensajeAProcesar);
	}
	else if (tipoMensaje == "P") unSerializePuntajes(mensajeAProcesar);
	else if (tipoMensaje == "S") unSerializeSonidos(mensajeAProcesar);

	else unSerializeUpdate(mensajeAProcesar, tipoMensaje.c_str()[0]);
}

void Cliente::setPuntajes(vector< pair<int, string> > puntajes) {
	
	pthread_mutex_lock(&mutexPuntajes);
	this->puntajes = puntajes;
	pthread_mutex_unlock(&mutexPuntajes);
}

void Cliente::setSonidos(vector< pair<vector< pair<int, int> >, Punto> > sonidos) {
	
	vector< pair< pair<bool, int>, Punto > > sonidosAux;
	for (unsigned int i=0; i<this->sonidos.size(); i++) {
		Punto posicion(0, 0);
		sonidosAux.push_back(pair< pair<bool, int>, Punto > (pair<bool, int>(false, 0), posicion));
	}
		
	for (unsigned int i=0; i<sonidos.size(); i++)
		for (unsigned int j=0; j<sonidos.at(i).first.size(); j++)
			sonidosAux[sonidos.at(i).first.at(j).first] = std::make_pair< pair<bool, int>, Punto >(pair<bool, int>(true, sonidos.at(i).first.at(j).second), sonidos.at(i).second);
			
	pthread_mutex_lock(&mutexSonidos);
	this->sonidos = sonidosAux;
	pthread_mutex_unlock(&mutexSonidos);
}

void Cliente::Jugar() {

	bool jugando = true;
	t_juego.start();
	
	pthread_t threadEscribe;
	pthread_t threadLee;
	
	setEstadoJuego(JuegoStart);

	if (pthread_create (&threadEscribe, NULL, ClienteAServidor, this) != 0) {
			cout << "No puedo crear thread para escribir. ERROR.";
			exit (1);
	}

	if (pthread_create (&threadLee, NULL, ServidorACliente, this) != 0) {
			cout << "No puedo crear thread para leer. ERROR.";
			exit (1);
	}
	
	SDL_Event evento;
	vector< pair < pair<idImagen_t, int> , Punto> > v_dibujables;

	vista->DibujarBarraEstado("");
	
	Uint32 lastUpdateTime = t_juego.getTick();
	pair<int, string> lastUpdate;
	
	vista->IniciarSonido();
	pair<int, string> update;
	while (jugando) {

		pthread_mutex_lock(&mutexDibujables);
		v_dibujables = dibujables;
		pthread_mutex_unlock(&mutexDibujables);
		vista->Dibujar(v_dibujables);
		
		pthread_mutex_lock(&mutexPuntajes);
		vector< pair<idImagen_t, string> > v_puntajes = puntajes;
		pthread_mutex_unlock(&mutexPuntajes);

		vista->DibujarPuntaje(v_puntajes);
		
		pthread_mutex_lock(&mutexSonidos);
		vector< pair< pair<bool, int>, Punto > > v_sonidos = sonidos;
		pthread_mutex_unlock(&mutexSonidos);
		vista->ReproducirSonidos(v_sonidos);
		
		if (t_juego.getTick() - lastUpdateTime > TIEMPO_ENTRE_UPDATES) {
			
			update = getNextUpdate();
			while (update.second == lastUpdate.second) {
				update = getNextUpdate();
				lastUpdateTime = t_juego.getTick();
			}
			
			if (update.first == UPDATE_FIN_JUEGO)
				setEstadoJuego(JuegoEnd);
				
			if (update.second.size() > 0) {
				lastUpdate = update;
				lastUpdateTime = t_juego.getTick();
			}
		}
		
		vista->DibujarBarraEstado(lastUpdate.second);

		while (SDL_PollEvent(&evento)) {
			controlador->AgregarEvento(evento);
            if( evento.type == SDL_QUIT )
            	setEstadoJuego(JuegoNext);

            if( evento.type == SDL_KEYDOWN )
                if( evento.key.keysym.sym == SDLK_ESCAPE )
                	setEstadoJuego(JuegoNext);
		}
		
		if (getEstadoJuego() == JuegoEnd) {
			vista->FinalizarJuego(update.second);
			setEstadoJuego(JuegoNext);
		}
		if (getEstadoJuego() == JuegoNext) {
			jugando = false;
			vista->DibujarBarraEstado("Finalizando juego...");
			SDL_Delay(1000);
			vista->DibujarBarraEstado("Volviendo al menu principal...");
			SDL_Delay(1500);
		} else if (getEstadoConexion() == Inactiva) {
			jugando = false;
			vista->DibujarBarraEstado("Juego finalizado por problemas de CONEXION");
			SDL_Delay(3000);
			vista->DibujarBarraEstado("Volviendo al menu principal...");
			SDL_Delay(2000);
		}
	}	
	
	vista->FinalizarSonido();
						
	void* statusLectura;
	void* statusEscritura;
	// Esperando fin de thread lectura...
	pthread_join(threadLee, &statusLectura);
	// Esperando fin de thread escritura...
	pthread_join(threadEscribe, &statusEscritura);
	
}

InputCliente* Cliente::getControlador() {
	
	return controlador;
}

int Cliente::getSocket() {
	
	return socket;
}

Uint32 Cliente::getTime() {
	
	return t_juego.getTick();
}

Pantalla* Cliente::getPantalla(){
	return this->pantalla;
}

int Cliente::getMargenScroll(){
	return this->margenScroll;
}

void Cliente::setEstadoJuego(EstadoJuegoCliente estado) {
	pthread_mutex_lock (&mutexEstadoJuego);
	estadoJuego = estado;
	pthread_mutex_unlock (&mutexEstadoJuego);
}

void Cliente::setEstadoConexion(EstadoConexion estado) {
	pthread_mutex_lock (&mutexEstadoConexion);
	estadoConexion = estado;
	pthread_mutex_unlock (&mutexEstadoConexion);
}

EstadoJuegoCliente Cliente::getEstadoJuego() {
	pthread_mutex_lock (&mutexEstadoJuego);
	EstadoJuegoCliente e = estadoJuego;
	pthread_mutex_unlock (&mutexEstadoJuego);
	return e;
}

EstadoConexion Cliente::getEstadoConexion() {
	pthread_mutex_lock (&mutexEstadoConexion);
	EstadoConexion e = estadoConexion;
	pthread_mutex_unlock (&mutexEstadoConexion);
	return e;
}

// *************************** //
// Funcion de thread Escritura //
// *************************** //
void* ClienteAServidor(void* data) {
	
	Cliente* cliente = (Cliente*) data;
	Uint32 tiempoUltimoMensaje = cliente->getTime();
	
	while (true) {
		
		SDL_Delay(DELAY_CABEZA);

		if (cliente->getEstadoConexion() == Inactiva || cliente->getEstadoJuego() == JuegoNext)
			break;
			
		cliente->getControlador()->manejarInput();

		string mensaje = cliente->getControlador()->getMensaje();
		if (mensaje.size() == 0) {
			if (cliente->getTime() - tiempoUltimoMensaje < TIEMPO_ENTRE_MENSAJES_CLIENT) continue;
			else mensaje = "0;0#";
		}

        int res = writeSocket(cliente->getSocket(), mensaje);
        tiempoUltimoMensaje = cliente->getTime();
		if (res == SIN_CONEXION) {
			cliente->setEstadoConexion(Inactiva);
			break;
		}

        cliente->getControlador()->limpiarBuffer();

	}
	
	return NULL;
}

// ************************* //
// Funcion de thread Lectura //
// ************************* //
void* ServidorACliente(void* data) {

	Cliente* cliente = (Cliente*) data;
	int largo = 0;
	Uint32 lastMessageTime = 0;

	while (true) {
		string updates = "";

		SDL_Delay(DELAY_CABEZA);

		ioctl(cliente->getSocket(), FIONREAD, &largo);

	    if (largo > 0) {
	    	int res = readSocket(cliente->getSocket(), &updates, largo);

	    	lastMessageTime = cliente->getTime();
	    	if (res == SIN_CONEXION) {
	    		cliente->setEstadoConexion(Inactiva);
	    		break;
	    	}
	    }
	    else {
	    	if ( cliente->getTime() - lastMessageTime > CLIENT_TIMEOUT ) {
	    		cliente->setEstadoConexion(Inactiva);
	    		break;
	    	}
	    }
	    
	    cliente->unSerialize(updates);

		if (cliente->getEstadoConexion() == Inactiva || cliente->getEstadoJuego() == JuegoNext)
			break;

	}
	return NULL;
}
